/*
 * public domain as of http://rsbweb.nih.gov/ij/disclaimer.html
 */
package com.github.junrar.io;

import java.io.*;
import java.util.Vector;

/**
 * This is a class that uses a memory cache to allow seeking within an
 * InputStream. Based on the JAI MemoryCacheSeekableStream class. Can also be
 * constructed from a RandomAccessFile, which uses less memory since the memory
 * cache is not required.
 */
@SuppressWarnings("rawtypes")
public final class RandomAccessStream extends InputStream
 {

  private static final int BLOCK_SIZE = 512;
  private static final int BLOCK_MASK = 511;
  private static final int BLOCK_SHIFT = 9;

  private InputStream src;
  private RandomAccessFile ras;
  private long pointer;
  private Vector data;
  private int length;
  private boolean foundEOS;

  /**
   * Constructs a RandomAccessStream from an InputStream. Seeking backwards is
   * supported using a memory cache.
   */
  public RandomAccessStream(InputStream inputstream)
   {
    pointer = 0L;
    data = new Vector();
    length = 0;
    foundEOS = false;
    src = inputstream;
   }

  /** Constructs a RandomAccessStream from an RandomAccessFile. */
  public RandomAccessStream(RandomAccessFile ras)
   {
    this.ras = ras;
   }

  public int getFilePointer() throws IOException
   {
    if (ras != null)
     return (int) ras.getFilePointer();
    else
     return (int) pointer;
   }

  public long getLongFilePointer() throws IOException
   {
    if (ras != null)
     return ras.getFilePointer();
    else
     return pointer;
   }

  public int read() throws IOException
   {
    if (ras != null)
     return ras.read();
    long l = pointer + 1L;
    long l1 = readUntil(l);
    if (l1 >= l)
     {
      byte abyte0[] = (byte[]) data.elementAt((int) (pointer >> BLOCK_SHIFT));
      return abyte0[(int) (pointer++ & BLOCK_MASK)] & 0xff;
     }
    else
     return -1;
   }

  public int read(byte[] bytes, int off, int len) throws IOException
   {
    if (bytes == null)
     throw new NullPointerException();
    if (ras != null)
     return ras.read(bytes, off, len);
    if (off < 0 || len < 0 || off + len > bytes.length)
     throw new IndexOutOfBoundsException();
    if (len == 0)
     return 0;
    long l = readUntil(pointer + len);
    if (l <= pointer)
     return -1;
    else
     {
      byte abyte1[] = (byte[]) data.elementAt((int) (pointer >> BLOCK_SHIFT));
      int k = Math.min(len, BLOCK_SIZE - (int) (pointer & BLOCK_MASK));
      System.arraycopy(abyte1, (int) (pointer & BLOCK_MASK), bytes, off, k);
      pointer += k;
      return k;
     }
   }

  public final void readFully(byte[] bytes) throws IOException
   {
    readFully(bytes, bytes.length);
   }

  public final void readFully(byte[] bytes, int len) throws IOException
   {
    int read = 0;
    do
     {
      int l = read(bytes, read, len - read);
      if (l < 0)
       break;
      read += l;
     }
    while (read < len);
   }

  @SuppressWarnings("unchecked")
  private long readUntil(long l) throws IOException
   {
    if (l < length)
     return l;
    if (foundEOS)
     return length;
    int i = (int) (l >> BLOCK_SHIFT);
    int j = length >> BLOCK_SHIFT;
    for (int k = j; k <= i; k++)
     {
      byte abyte0[] = new byte[BLOCK_SIZE];
      data.addElement(abyte0);
      int i1 = BLOCK_SIZE;
      int j1 = 0;
      while (i1 > 0)
       {
        int k1 = src.read(abyte0, j1, i1);
        if (k1 == -1)
         {
          foundEOS = true;
          return length;
         }
        j1 += k1;
        i1 -= k1;
        length += k1;
       }

     }

    return length;
   }

  public void seek(long loc) throws IOException
   {
    if (ras != null)
     {
      ras.seek(loc);
      return;
     }
    if (loc < 0L)
     pointer = 0L;
    else
     pointer = loc;
   }

  public void seek(int loc) throws IOException
   {
    long lloc = ((long) loc) & 0xffffffffL;
    if (ras != null)
     {
      ras.seek(lloc);
      return;
     }
    if (lloc < 0L)
     pointer = 0L;
    else
     pointer = lloc;
   }

  public final int readInt() throws IOException
   {
    int i = read();
    int j = read();
    int k = read();
    int l = read();
    if ((i | j | k | l) < 0)
     throw new EOFException();
    else
     return (i << 24) + (j << 16) + (k << 8) + l;
   }

  public final long readLong() throws IOException
   {
    return ((long) readInt() << 32) + ((long) readInt() & 0xffffffffL);
   }

  public final double readDouble() throws IOException
   {
    return Double.longBitsToDouble(readLong());
   }

  public final short readShort() throws IOException
   {
    int i = read();
    int j = read();
    if ((i | j) < 0)
     throw new EOFException();
    else
     return (short) ((i << 8) + j);
   }

  public final float readFloat() throws IOException
   {
    return Float.intBitsToFloat(readInt());
   }

  public void close() throws IOException
   {
    if (ras != null)
     ras.close();
    else
     {
      data.removeAllElements();
      src.close();
     }
   }

 }